﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using WebAnalyser.UI;
using WebAnalyser.Api.Interfaces;

namespace WebAnalyser.Library
{
    using GrabMethodDelegate = Func<string, string, string>;

    public class Plugins
    {
        private const string Path = @".\Plugins\";
        private Dictionary<string, string> _pluginsDlls = new Dictionary<string, string>();

        private readonly Dictionary<string, IPluginDefinition> _pluginsDefinitions =
            new Dictionary<string, IPluginDefinition>();
        /// <summary>
        /// Set of plugins Definitions.
        /// </summary>
        public Dictionary<string, IPluginDefinition> PluginsDefinitions
        {
            get { return _pluginsDefinitions; }
        }

        private readonly Dictionary<string, GrabMethodDelegate> _grabMethodCache =
            new Dictionary<string, GrabMethodDelegate>();
        /// <summary>
        /// Set of grab step methods.
        /// </summary>
        public Dictionary<string, GrabMethodDelegate> GrabMethodCache
        {
            get { return _grabMethodCache; }
        }

        #region Constructor
        private Plugins()
        {
            Debug.WriteLine("plugin class instanced");
        }

        ~Plugins()
        {
            Debug.WriteLine("plugin class disposed");
        }
        #endregion Constructor

        #region Singleton
        private static readonly Object SyncLock = new Object();

        private static volatile Plugins _instance;
        public static Plugins Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncLock)
                    {
                        if (_instance == null)
                        {
                            _instance = new Plugins();
                        }
                    }
                }
                return _instance;
            }
        }
        #endregion Singleton

        /// <summary>
        /// Initialize plugins (prepare caches & information lists).
        /// </summary>
        public void Init()
        {
            string[] files = Directory.GetFiles(Path);
            _pluginsDlls = Parser.RemoveNoPlugin(files, _pluginsDlls);

            foreach (KeyValuePair<string, string> plugin in _pluginsDlls)
            {
                Assembly assembly = Assembly.LoadFrom(plugin.Value);

                try
                {
                    _pluginsDefinitions.Add(plugin.Key, GetPluginDefinition(assembly));
                }
                catch (Exception)
                {
                    Debug.WriteLine("Can't load plugin definition in: " + plugin.Value);
                    continue;
                }

                try
                {
                    _grabMethodCache.Add(plugin.Key,
                                         (GrabMethodDelegate)
                                         SetMethodInCache<GrabMethodDelegate>(assembly, "Grab", "GrabMethod"));
                }
                catch (Exception)
                {
                    _pluginsDefinitions.Remove(plugin.Key);
                    Debug.WriteLine("Can't load grab method in plugin: " + plugin.Value);
                    continue;
                }
            }
        }

        /// <summary>
        /// Gets the plugin definition.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns></returns>
        private static IPluginDefinition GetPluginDefinition(Assembly assembly)
        {
            foreach (Type type in assembly.GetTypes())
            {
                if (type.IsClass)
                {
                    if (type.FullName != null)
                    {
                        if (type.FullName.EndsWith("PluginDefinition"))
                        {
                            try
                            {
                                return (IPluginDefinition)Activator.CreateInstance(type);
                            }
                            catch (Exception e)
                            {
                                Debug.WriteLine(e.Message + type.FullName);
                            }
                        }
                    }
                }
            }
            throw (new Exception("Unable to find requested method"));
        }

        /// <summary>
        /// Set a method in cache.
        /// </summary>
        /// <typeparam name="T">Delegate method type.</typeparam>
        /// <param name="assembly">Targeted assembly.</param>
        /// <param name="namespaceName">Targeted namespace.</param>
        /// <param name="methodName">Targeted method name.</param>
        /// <returns>Method's delegate.</returns>
        private static object SetMethodInCache<T>(Assembly assembly, string namespaceName, string methodName)
        {
            foreach (Type type in assembly.GetTypes())
            {
                if (type.IsClass)
                {
                    if (type.FullName != null)
                    {
                        if (type.FullName.EndsWith("." + namespaceName))
                        {
                            MethodInfo methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
                            try
                            {
                                methodInfo = methodInfo.MakeGenericMethod(type);
                                return Delegate.CreateDelegate(typeof (T), methodInfo);
                            }
                            catch (Exception e)
                            {
                                Debug.WriteLine("-----\n" + e.GetType() + "\n" + e.Message + "\n-----");
                            }
                        }
                    }
                }
            }
            throw (new Exception("Unable to find requested method"));
        }
    }
}
